
#pragma once

#include <windows.h>
#include <strsafe.h>

#pragma comment( lib, "user32.lib") 
#pragma comment( lib, "gdi32.lib")

#define NUMHOOKS 7 

#define IDM_CALLWNDPROC 0
#define IDM_CBT 1
#define IDM_DEBUG 2
#define IDM_GETMESSAGE 3
#define IDM_KEYBOARD 4
#define IDM_MOUSE 5
#define IDM_MSGFILTER 6

// Global variables 

typedef struct _MYHOOKDATA
{
	int nType;
	HOOKPROC hkprc;
	HHOOK hhook;
} MYHOOKDATA;

MYHOOKDATA myhookdata[NUMHOOKS];

HWND gh_hwndMain;

// Hook procedures

LRESULT WINAPI CallWndProc(int, WPARAM, LPARAM);
LRESULT WINAPI CBTProc(int, WPARAM, LPARAM);
LRESULT WINAPI DebugProc(int, WPARAM, LPARAM);
LRESULT WINAPI GetMsgProc(int, WPARAM, LPARAM);
LRESULT WINAPI KeyboardProc(int, WPARAM, LPARAM);
LRESULT WINAPI MouseProc(int, WPARAM, LPARAM);
LRESULT WINAPI MessageProc(int, WPARAM, LPARAM);

void LookUpTheMessage(PMSG, LPTSTR);

LRESULT WINAPI MainWndProc(HWND hwndMain, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static BOOL afHooks[NUMHOOKS];
	int index;
	static HMENU hmenu;

	gh_hwndMain = hwndMain;

	switch (uMsg)
	{
	case WM_CREATE:

		// Save the menu handle

		hmenu = GetMenu(hwndMain);

		// Initialize structures with hook data. The menu-item identifiers are 
		// defined as 0 through 6 in the header file app.h. They can be used to 
		// identify array elements both here and during the WM_COMMAND message. 

		myhookdata[IDM_CALLWNDPROC].nType = WH_CALLWNDPROC;
		myhookdata[IDM_CALLWNDPROC].hkprc = CallWndProc;
		myhookdata[IDM_CBT].nType = WH_CBT;
		myhookdata[IDM_CBT].hkprc = CBTProc;
		myhookdata[IDM_DEBUG].nType = WH_DEBUG;
		myhookdata[IDM_DEBUG].hkprc = DebugProc;
		myhookdata[IDM_GETMESSAGE].nType = WH_GETMESSAGE;
		myhookdata[IDM_GETMESSAGE].hkprc = GetMsgProc;
		myhookdata[IDM_KEYBOARD].nType = WH_KEYBOARD;
		myhookdata[IDM_KEYBOARD].hkprc = KeyboardProc;
		myhookdata[IDM_MOUSE].nType = WH_MOUSE;
		myhookdata[IDM_MOUSE].hkprc = MouseProc;
		myhookdata[IDM_MSGFILTER].nType = WH_MSGFILTER;
		myhookdata[IDM_MSGFILTER].hkprc = MessageProc;

		// Initialize all flags in the array to FALSE. 

		memset(afHooks, FALSE, sizeof(afHooks));

		return 0;

	case WM_COMMAND:
		switch (LOWORD(wParam))
		{
			// The user selected a hook command from the menu. 

		case IDM_CALLWNDPROC:
		case IDM_CBT:
		case IDM_DEBUG:
		case IDM_GETMESSAGE:
		case IDM_KEYBOARD:
		case IDM_MOUSE:
		case IDM_MSGFILTER:

			// Use the menu-item identifier as an index 
			// into the array of structures with hook data. 

			index = LOWORD(wParam);

			// If the selected type of hook procedure isn't 
			// installed yet, install it and check the 
			// associated menu item. 

			if (!afHooks[index])
			{
				myhookdata[index].hhook = SetWindowsHookEx(
					myhookdata[index].nType,
					myhookdata[index].hkprc,
					(HINSTANCE)NULL, GetCurrentThreadId());
				CheckMenuItem(hmenu, index,
					MF_BYCOMMAND | MF_CHECKED);
				afHooks[index] = TRUE;
			}

			// If the selected type of hook procedure is 
			// already installed, remove it and remove the 
			// check mark from the associated menu item. 

			else
			{
				UnhookWindowsHookEx(myhookdata[index].hhook);
				CheckMenuItem(hmenu, index,
					MF_BYCOMMAND | MF_UNCHECKED);
				afHooks[index] = FALSE;
			}

		default:
			return (DefWindowProc(hwndMain, uMsg, wParam,
				lParam));
		}
		break;

		//
		// Process other messages. 
		//

	default:
		return DefWindowProc(hwndMain, uMsg, wParam, lParam);
	}
	return NULL;
}

/****************************************************************
  WH_CALLWNDPROC hook procedure
 ****************************************************************/

LRESULT WINAPI CallWndProc(int nCode, WPARAM wParam, LPARAM lParam)
{
	CHAR szCWPBuf[256];
	CHAR szMsg[16];
	HDC hdc;
	static int c = 0;
	size_t cch;
	HRESULT hResult;

	if (nCode < 0)  // do not process message 
		return CallNextHookEx(myhookdata[IDM_CALLWNDPROC].hhook, nCode, wParam, lParam);

	// Call an application-defined function that converts a message 
	// constant to a string and copies it to a buffer. 

	LookUpTheMessage((PMSG)lParam, (LPTSTR)szMsg);

	hdc = GetDC(gh_hwndMain);

	switch (nCode)
	{
	case HC_ACTION:
		hResult = StringCchPrintf((STRSAFE_LPWSTR)szCWPBuf, 256 / sizeof(TCHAR),
			(STRSAFE_LPWSTR)"CALLWNDPROC - tsk: %ld, msg: %s, %d times   ",
			wParam, szMsg, c++);
		if (FAILED(hResult))
		{
			// TODO: writer error handler
		}
		hResult = StringCchLength((STRSAFE_LPWSTR)szCWPBuf, 256 / sizeof(TCHAR), &cch);
		if (FAILED(hResult))
		{
			// TODO: write error handler
		}
		TextOut(hdc, 2, 15, (STRSAFE_LPWSTR)szCWPBuf, cch);
		break;

	default:
		break;
	}

	ReleaseDC(gh_hwndMain, hdc);

	return CallNextHookEx(myhookdata[IDM_CALLWNDPROC].hhook, nCode, wParam, lParam);
}

/****************************************************************
  WH_GETMESSAGE hook procedure
 ****************************************************************/

LRESULT CALLBACK GetMsgProc(int nCode, WPARAM wParam, LPARAM lParam)
{
	CHAR szMSGBuf[256];
	CHAR szRem[16];
	CHAR szMsg[16];
	HDC hdc = nullptr;
	static int c = 0;
	size_t cch;
	HRESULT hResult;

	if (nCode < 0) // do not process message 
		return CallNextHookEx(myhookdata[IDM_GETMESSAGE].hhook, nCode,
			wParam, lParam);

	switch (nCode)
	{
	case HC_ACTION:
		switch (wParam)
		{
		case PM_REMOVE:
			hResult = StringCchCopy((STRSAFE_LPWSTR)szRem, 16 / sizeof(TCHAR), (STRSAFE_LPWSTR)"PM_REMOVE");
			if (FAILED(hResult))
			{
				// TODO: write error handler
			}
			break;

		case PM_NOREMOVE:
			hResult = StringCchCopy((STRSAFE_LPWSTR)szRem, 16 / sizeof(TCHAR), (STRSAFE_LPWSTR)"PM_NOREMOVE");
			if (FAILED(hResult))
			{
				// TODO: write error handler
			}
			break;

		default:
			hResult = StringCchCopy((STRSAFE_LPWSTR)szRem, 16 / sizeof(TCHAR), (STRSAFE_LPWSTR)"Unknown");
			if (FAILED(hResult))
			{
				// TODO: write error handler
			}
			break;
		}

		// Call an application-defined function that converts a 
		// message constant to a string and copies it to a 
		// buffer. 

		LookUpTheMessage((PMSG)lParam, (STRSAFE_LPWSTR)szMsg);

		hdc = GetDC(gh_hwndMain);
		hResult = StringCchPrintf((STRSAFE_LPWSTR)szMSGBuf, 256 / sizeof(TCHAR),
			(STRSAFE_LPWSTR)"GETMESSAGE - wParam: %s, msg: %s, %d times   ",
			szRem, szMsg, c++);
		if (FAILED(hResult))
		{
			// TODO: write error handler
		}
		hResult = StringCchLength((STRSAFE_LPWSTR)szMSGBuf, 256 / sizeof(TCHAR), &cch);
		if (FAILED(hResult))
		{
			// TODO: write error handler
		}
		TextOut(hdc, 2, 35, (STRSAFE_LPWSTR)szMSGBuf, cch);
		break;

	default:
		break;
	}

	if (hdc != nullptr)
		ReleaseDC(gh_hwndMain, hdc);

	return CallNextHookEx(myhookdata[IDM_GETMESSAGE].hhook, nCode, wParam, lParam);
}

/****************************************************************
  WH_DEBUG hook procedure
 ****************************************************************/

LRESULT CALLBACK DebugProc(int nCode, WPARAM wParam, LPARAM lParam)
{
	CHAR szBuf[128];
	HDC hdc;
	static int c = 0;
	size_t cch;
	HRESULT hResult;

	if (nCode < 0)  // do not process message 
		return CallNextHookEx(myhookdata[IDM_DEBUG].hhook, nCode,
			wParam, lParam);

	hdc = GetDC(gh_hwndMain);

	switch (nCode)
	{
	case HC_ACTION:
		hResult = StringCchPrintf((STRSAFE_LPWSTR)szBuf, 128 / sizeof(TCHAR),
			(STRSAFE_LPWSTR)"DEBUG - nCode: %d, tsk: %ld, %d times   ",
			nCode, wParam, c++);
		if (FAILED(hResult))
		{
			// TODO: write error handler
		}
		hResult = StringCchLength((STRSAFE_LPWSTR)szBuf, 128 / sizeof(TCHAR), &cch);
		if (FAILED(hResult))
		{
			// TODO: write error handler
		}
		TextOut(hdc, 2, 55, (STRSAFE_LPWSTR)szBuf, cch);
		break;

	default:
		break;
	}

	ReleaseDC(gh_hwndMain, hdc);

	return CallNextHookEx(myhookdata[IDM_DEBUG].hhook, nCode, wParam, lParam);
}

/****************************************************************
  WH_CBT hook procedure
 ****************************************************************/

LRESULT CALLBACK CBTProc(int nCode, WPARAM wParam, LPARAM lParam)
{
	CHAR szBuf[128];
	CHAR szCode[128];
	HDC hdc;
	static int c = 0;
	size_t cch;
	HRESULT hResult;

	if (nCode < 0)  // do not process message 
		return CallNextHookEx(myhookdata[IDM_CBT].hhook, nCode, wParam,
			lParam);

	hdc = GetDC(gh_hwndMain);

	switch (nCode)
	{
	case HCBT_ACTIVATE:
		hResult = StringCchCopy((STRSAFE_LPWSTR)szCode, 128 / sizeof(TCHAR), (STRSAFE_LPWSTR)"HCBT_ACTIVATE");
		if (FAILED(hResult))
		{
			// TODO: write error handler
		}
		break;

	case HCBT_CLICKSKIPPED:
		hResult = StringCchCopy((STRSAFE_LPWSTR)szCode, 128 / sizeof(TCHAR), (STRSAFE_LPWSTR)"HCBT_CLICKSKIPPED");
		if (FAILED(hResult))
		{
			// TODO: write error handler
		}
		break;

	case HCBT_CREATEWND:
		hResult = StringCchCopy((STRSAFE_LPWSTR)szCode, 128 / sizeof(TCHAR), (STRSAFE_LPWSTR)"HCBT_CREATEWND");
		if (FAILED(hResult))
		{
			// TODO: write error handler
		}
		break;

	case HCBT_DESTROYWND:
		hResult = StringCchCopy((STRSAFE_LPWSTR)szCode, 128 / sizeof(TCHAR), (STRSAFE_LPWSTR)"HCBT_DESTROYWND");
		if (FAILED(hResult))
		{
			// TODO: write error handler
		}
		break;

	case HCBT_KEYSKIPPED:
		hResult = StringCchCopy((STRSAFE_LPWSTR)szCode, 128 / sizeof(TCHAR), (STRSAFE_LPWSTR)"HCBT_KEYSKIPPED");
		if (FAILED(hResult))
		{
			// TODO: write error handler
		}
		break;

	case HCBT_MINMAX:
		hResult = StringCchCopy((STRSAFE_LPWSTR)szCode, 128 / sizeof(TCHAR), (STRSAFE_LPWSTR)"HCBT_MINMAX");
		if (FAILED(hResult))
		{
			// TODO: write error handler
		}
		break;

	case HCBT_MOVESIZE:
		hResult = StringCchCopy((STRSAFE_LPWSTR)szCode, 128 / sizeof(TCHAR), (STRSAFE_LPWSTR)"HCBT_MOVESIZE");
		if (FAILED(hResult))
		{
			// TODO: write error handler
		}
		break;

	case HCBT_QS:
		hResult = StringCchCopy((STRSAFE_LPWSTR)szCode, 128 / sizeof(TCHAR), (STRSAFE_LPWSTR)"HCBT_QS");
		if (FAILED(hResult))
		{
			// TODO: write error handler
		}
		break;

	case HCBT_SETFOCUS:
		hResult = StringCchCopy((STRSAFE_LPWSTR)szCode, 128 / sizeof(TCHAR), (STRSAFE_LPWSTR)"HCBT_SETFOCUS");
		if (FAILED(hResult))
		{
			// TODO: write error handler
		}
		break;

	case HCBT_SYSCOMMAND:
		hResult = StringCchCopy((STRSAFE_LPWSTR)szCode, 128 / sizeof(TCHAR), (STRSAFE_LPWSTR)"HCBT_SYSCOMMAND");
		if (FAILED(hResult))
		{
			// TODO: write error handler
		}
		break;

	default:
		hResult = StringCchCopy((STRSAFE_LPWSTR)szCode, 128 / sizeof(TCHAR), (STRSAFE_LPWSTR)"Unknown");
		if (FAILED(hResult))
		{
			// TODO: write error handler
		}
		break;
	}
	hResult = StringCchPrintf((STRSAFE_LPWSTR)szBuf, 128 / sizeof(TCHAR), (STRSAFE_LPWSTR)"CBT -  nCode: %s, tsk: %ld, %d times   ",
		szCode, wParam, c++);
	if (FAILED(hResult))
	{
		// TODO: write error handler
	}
	hResult = StringCchLength((STRSAFE_LPWSTR)szBuf, 128 / sizeof(TCHAR), &cch);
	if (FAILED(hResult))
	{
		// TODO: write error handler
	}
	TextOut(hdc, 2, 75, (STRSAFE_LPWSTR)szBuf, cch);
	ReleaseDC(gh_hwndMain, hdc);

	return CallNextHookEx(myhookdata[IDM_CBT].hhook, nCode, wParam, lParam);
}

/****************************************************************
  WH_MOUSE hook procedure
 ****************************************************************/

LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam)
{
	CHAR szBuf[128];
	CHAR szMsg[16];
	HDC hdc;
	static int c = 0;
	size_t cch;
	HRESULT hResult;

	if (nCode < 0)  // do not process the message 
		return CallNextHookEx(myhookdata[IDM_MOUSE].hhook, nCode,
			wParam, lParam);

	// Call an application-defined function that converts a message 
	// constant to a string and copies it to a buffer. 

	LookUpTheMessage((PMSG)lParam, (STRSAFE_LPWSTR)szMsg);

	hdc = GetDC(gh_hwndMain);
	hResult = StringCchPrintf((STRSAFE_LPWSTR)szBuf, 128 / sizeof(TCHAR),
		(STRSAFE_LPWSTR)"MOUSE - nCode: %d, msg: %s, x: %d, y: %d, %d times   ",
		nCode, szMsg, LOWORD(lParam), HIWORD(lParam), c++);
	if (FAILED(hResult))
	{
		// TODO: write error handler
	}
	hResult = StringCchLength((STRSAFE_LPWSTR)szBuf, 128 / sizeof(TCHAR), &cch);
	if (FAILED(hResult))
	{
		// TODO: write error handler
	}
	TextOut(hdc, 2, 95, (STRSAFE_LPWSTR)szBuf, cch);
	ReleaseDC(gh_hwndMain, hdc);

	return CallNextHookEx(myhookdata[IDM_MOUSE].hhook, nCode, wParam, lParam);
}

/****************************************************************
  WH_KEYBOARD hook procedure
 ****************************************************************/

LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
	CHAR szBuf[128];
	HDC hdc;
	static int c = 0;
	size_t cch;
	HRESULT hResult;

	if (nCode < 0)  // do not process message 
		return CallNextHookEx(myhookdata[IDM_KEYBOARD].hhook, nCode,
			wParam, lParam);

	hdc = GetDC(gh_hwndMain);
	hResult = StringCchPrintf((STRSAFE_LPWSTR)szBuf, 128 / sizeof(TCHAR), (STRSAFE_LPWSTR)"KEYBOARD - nCode: %d, vk: %d, %d times ", nCode, wParam, c++);
	if (FAILED(hResult))
	{
		// TODO: write error handler
	}
	hResult = StringCchLength((STRSAFE_LPWSTR)szBuf, 128 / sizeof(TCHAR), &cch);
	if (FAILED(hResult))
	{
		// TODO: write error handler
	}
	TextOut(hdc, 2, 115, (STRSAFE_LPWSTR)szBuf, cch);
	ReleaseDC(gh_hwndMain, hdc);

	return CallNextHookEx(myhookdata[IDM_KEYBOARD].hhook, nCode, wParam, lParam);
}

/****************************************************************
  WH_MSGFILTER hook procedure
 ****************************************************************/

LRESULT CALLBACK MessageProc(int nCode, WPARAM wParam, LPARAM lParam)
{
	CHAR szBuf[128];
	CHAR szMsg[16];
	CHAR szCode[32];
	HDC hdc;
	static int c = 0;
	size_t cch;
	HRESULT hResult;

	if (nCode < 0)  // do not process message 
		return CallNextHookEx(myhookdata[IDM_MSGFILTER].hhook, nCode,
			wParam, lParam);

	switch (nCode)
	{
	case MSGF_DIALOGBOX:
		hResult = StringCchCopy((STRSAFE_LPWSTR)szCode, 32 / sizeof(TCHAR), (STRSAFE_LPWSTR)"MSGF_DIALOGBOX");
		if (FAILED(hResult))
		{
			// TODO: write error handler
		}
		break;

	case MSGF_MENU:
		hResult = StringCchCopy((STRSAFE_LPWSTR)szCode, 32 / sizeof(TCHAR), (STRSAFE_LPWSTR)"MSGF_MENU");
		if (FAILED(hResult))
		{
			// TODO: write error handler
		}
		break;

	case MSGF_SCROLLBAR:
		hResult = StringCchCopy((STRSAFE_LPWSTR)szCode, 32 / sizeof(TCHAR), (STRSAFE_LPWSTR)"MSGF_SCROLLBAR");
		if (FAILED(hResult))
		{
			// TODO: write error handler
		}
		break;

	default:
		hResult = StringCchPrintf((STRSAFE_LPWSTR)szCode, 128 / sizeof(TCHAR), (STRSAFE_LPWSTR)"Unknown: %d", nCode);
		if (FAILED(hResult))
		{
			// TODO: write error handler
		}
		break;
	}

	// Call an application-defined function that converts a message 
	// constant to a string and copies it to a buffer. 

	LookUpTheMessage((PMSG)lParam, (STRSAFE_LPWSTR)szMsg);

	hdc = GetDC(gh_hwndMain);
	hResult = StringCchPrintf((STRSAFE_LPWSTR)szBuf, 128 / sizeof(TCHAR),
		(STRSAFE_LPWSTR)"MSGFILTER  nCode: %s, msg: %s, %d times    ",
		szCode, szMsg, c++);
	if (FAILED(hResult))
	{
		// TODO: write error handler
	}
	hResult = StringCchLength((STRSAFE_LPWSTR)szBuf, 128 / sizeof(TCHAR), &cch);
	if (FAILED(hResult))
	{
		// TODO: write error handler
	}
	TextOut(hdc, 2, 135, (STRSAFE_LPWSTR)szBuf, cch);
	ReleaseDC(gh_hwndMain, hdc);

	return CallNextHookEx(myhookdata[IDM_MSGFILTER].hhook, nCode, wParam, lParam);
}